/*
 jChecs: a Java chess game sample 

 Copyright (C) 2006-2011 by David Cotton

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package fr.free.jchecs.ai;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import fr.free.jchecs.core.Board;
import fr.free.jchecs.core.Move;
import fr.free.jchecs.core.MoveGenerator;

/**
 * Moteur IA de debugage : recherche en dur le meilleur de l'ensemble des coups
 * possibles avec une profondeur de 3 demi-coups.
 * 
 * @author David Cotton
 */
final class DebugEngine extends AbstractEngine {
	/**
	 * Instancie un nouveau moteur IA de debugage.
	 */
	DebugEngine() {
		super(3, 3, 3);

		setMoveSorter(new StaticMoveSorter());
	}

	/**
	 * Corps de la recherche du "meilleur" demi-coup pour un état de
	 * l'échiquier.
	 * 
	 * @param pEtat
	 *            Etat de l'échiquier.
	 * @param pCoups
	 *            Liste des mouvement initiaux valides.
	 * @return Mouvement trouvé.
	 */
	@Override
	protected Move searchMoveFor(final MoveGenerator pEtat, final Move[] pCoups) {
		assert pEtat != null;
		assert pCoups != null;

		final int l = pCoups.length;
		assert l > 0;
		final Map<Board, BoardScore> echiquiersRang1 = new HashMap<Board, BoardScore>(
				l);
		final Map<Board, BoardScore> echiquiersRang2 = new HashMap<Board, BoardScore>(
				l * 25);
		final List<BoardScore> echiquiersRang3 = new ArrayList<BoardScore>(
				l * 25 * 25);
		Move res = pCoups[0];
		addHalfmove(l);
		for (final Move mvt1 : pCoups) {
			final MoveGenerator etat1 = pEtat.derive(mvt1, true);
			echiquiersRang1.put(etat1, new BoardScore(mvt1, null,
					Integer.MAX_VALUE));
			final Move[] coups2 = etat1.getValidMoves(etat1.isWhiteActive());
			addHalfmove(coups2.length);
			for (final Move mvt2 : coups2) {
				final MoveGenerator etat2 = etat1.derive(mvt2, true);
				echiquiersRang2.put(etat2, new BoardScore(mvt2, etat1,
						Integer.MIN_VALUE));
				final Move[] coups3 = etat2
						.getValidMoves(etat2.isWhiteActive());
				addHalfmove(coups3.length);
				for (final Move mvt3 : coups3) {
					final MoveGenerator etat3 = etat2.derive(mvt3, true);
					final BoardScore bs = new BoardScore(mvt3, etat2,
							getHeuristic().evaluate(etat3,
									!etat3.isWhiteActive()));
					echiquiersRang3.add(bs);
				}
			}
		}

		for (final BoardScore bs : echiquiersRang3) {
			final BoardScore parent = echiquiersRang2.get(bs.getParent());
			if (parent.getNote() < bs.getNote()) {
				parent.setNote(bs.getNote());
			}
		}

		for (final BoardScore bs : echiquiersRang2.values()) {
			final BoardScore parent = echiquiersRang1.get(bs.getParent());
			if (parent.getNote() > bs.getNote()) {
				parent.setNote(bs.getNote());
			}
		}

		int meilleur = Integer.MIN_VALUE;
		for (final BoardScore bs : echiquiersRang1.values()) {
			if (meilleur < bs.getNote()) {
				meilleur = bs.getNote();
				res = bs.getMove();
			}
		}

		setScore(meilleur);

		assert l == echiquiersRang1.values().size();
		assert res != null;
		return res;
	}

	/**
	 * Score d'un état.
	 */
	private static final class BoardScore {
		/** Mouvement précédant cet échiquier. */
		private final Move _move;

		/** Eventuel état père. */
		private final Board _parent;

		/** Note de l'échiquier. */
		private int _note;

		/**
		 * Instancie un nouveau score d'échiquier.
		 * 
		 * @param pMouvement
		 *            Mouvement précédant.
		 * @param pParent
		 *            Eventuel état père (peut être à null).
		 * @param pNote
		 *            Note initiale.
		 */
		BoardScore(final Move pMouvement, final Board pParent, final int pNote) {
			assert pMouvement != null;

			_move = pMouvement;
			_parent = pParent;
			_note = pNote;
		}

		/**
		 * Renvoi le mouvement lié.
		 * 
		 * @return Mouvement lié.
		 */
		Move getMove() {
			assert _move != null;
			return _move;
		}

		/**
		 * Renvoi la note de l'échiquier.
		 * 
		 * @return Note de l'échiquier.
		 */
		int getNote() {
			return _note;
		}

		/**
		 * Renvoi l'éventuel état père.
		 * 
		 * @return Etat père (peut être à null).
		 */
		Board getParent() {
			return _parent;
		}

		/**
		 * Alimente la note de l'échiquier.
		 * 
		 * @param pNote
		 *            Note de l'échiquier.
		 */
		void setNote(final int pNote) {
			_note = pNote;
		}
	}
}
